Pemfaktoran ulang monolit menjadi microservice

Panduan referensi ini adalah bagian kedua dari empat bagian mengenai mendesain, membangun, dan men-deploy microservice. Rangkaian ini menjelaskan berbagai elemen arsitektur microservice. Seri ini mencakup informasi tentang manfaat dan kelemahan pola arsitektur microservice, dan cara menerapkannya.

  1. Pengantar microservice
  2. Pemfaktoran ulang monolit menjadi microservice (dokumen ini)
  3. Komunikasi antarlayanan dalam penyiapan microservice
  4. Pelacakan terdistribusi di aplikasi microservice

Seri ini ditujukan bagi developer dan arsitek aplikasi yang mendesain dan mengimplementasikan migrasi untuk memfaktorkan ulang aplikasi monolit menjadi aplikasi microservice.

Proses transformasi aplikasi monolitik menjadi microservice adalah bentuk modernisasi aplikasi. Untuk menyelesaikan modernisasi aplikasi, sebaiknya jangan memfaktor ulang semua kode secara bersamaan. Sebagai gantinya, sebaiknya Anda memfaktor ulang aplikasi monolitik secara bertahap. Saat Anda memfaktor ulang aplikasi secara bertahap, Anda akan membangun aplikasi baru yang terdiri dari microservice secara bertahap, dan menjalankan aplikasi tersebut bersama dengan aplikasi monolitik Anda. Pendekatan ini juga dikenal sebagai pola Stangler Fig. Seiring dengan waktu, jumlah fungsi yang diimplementasikan oleh aplikasi monolitik akan menyusut hingga menghilang sepenuhnya atau menjadi microservice lain.

Untuk memisahkan kemampuan dari monolit, Anda harus mengekstrak data, logika, dan komponen yang ditampilkan kepada pengguna dengan cermat, lalu mengalihkannya ke layanan baru. Anda harus memiliki pemahaman yang baik tentang ruang masalah sebelum pindah ke ruang solusi.

Saat memahami ruang masalah, Anda memahami batas-batas alami di domain yang memberikan tingkat isolasi yang tepat. Sebaiknya Anda membuat layanan yang lebih besar, bukan layanan yang lebih kecil, sampai Anda memahami domain tersebut secara menyeluruh.

Menentukan batas layanan merupakan proses yang dilakukan berulang. Karena proses ini bukanlah pekerjaan mudah, Anda harus terus mengevaluasi biaya pemisahannya berdasarkan manfaat yang Anda dapatkan. Berikut adalah faktor-faktor yang membantu Anda mengevaluasi cara pemisahan monolit:

  • Hindari pemfaktoran ulang semua unsur sekaligus. Untuk memprioritaskan pemisahan layanan, evaluasi biaya versus manfaat.
  • Layanan dalam arsitektur microservice diatur berdasarkan masalah bisnis, bukan masalah teknis.
  • Saat Anda memigrasikan layanan secara bertahap, konfigurasikan komunikasi antara layanan dan monolit untuk melalui kontrak API yang telah ditetapkan dengan baik.
  • Microservice memerlukan lebih banyak otomatisasi: pikirkan terlebih dahulu tentang continuous integration (CI), continuous deployment (CD), logging terpusat, dan pemantauan singkat.

Bagian berikut membahas berbagai strategi untuk memisahkan layanan dan memigrasikan aplikasi monolitik Anda secara bertahap.

Memisahkan menurut desain berbasis domain

Microservice harus dirancang berdasarkan kemampuan bisnis, bukan lapisan horizontal seperti akses data atau pesan. Microservice juga harus memiliki pengaitan longgar dan kohesi fungsional yang tinggi. Microservice dikaitkan secara longgar jika Anda dapat mengubah satu layanan tanpa perlu mengupdate layanan lain secara bersamaan. Microservice bersifat kohesif jika memiliki satu tujuan yang jelas, seperti mengelola akun pengguna atau memproses pembayaran.

Desain yang berbasis domain.Domain-driver design (DDD) memerlukan pemahaman yang baik tentang domain yang menjadi tujuan penulisan aplikasi. Pengetahuan domain yang diperlukan untuk membuat aplikasi berada di orang-orang yang memahaminya—ahli domain.

Anda dapat menerapkan pendekatan DDD secara surut ke aplikasi yang ada sebagai berikut:

  1. Identifikasi bahasa di mana-mana—kosakata umum yang digunakan bersama oleh semua pemangku kepentingan. Sebagai developer, penting untuk menggunakan istilah dalam kode Anda yang dapat dipahami oleh orang non-teknis. Apa yang ingin dicapai oleh kode Anda harus merupakan cerminan dari proses perusahaan Anda.
  2. Identifikasi modul yang relevan dalam aplikasi monolitik, lalu terapkan kosakata umum ke modul tersebut.
  3. Tentukan konteks terbatas tempat Anda menerapkan batas eksplisit ke modul yang diidentifikasi dengan tanggung jawab yang jelas. Konteks terbatas yang Anda identifikasi adalah kandidat yang akan difaktorkan ulang menjadi microservice yang lebih kecil.

Diagram berikut menunjukkan cara menerapkan konteks terikat ke aplikasi e-commerce yang ada:

Konteks terbatas diterapkan ke aplikasi.

Gambar 1. Kemampuan aplikasi dipisahkan ke dalam konteks terbatas yang bermigrasi ke layanan.

Pada gambar 1, kemampuan aplikasi e-commerce dipisahkan ke dalam konteks terbatas dan dimigrasikan ke layanan sebagai berikut:

  • Kemampuan pengelolaan dan pemenuhan pesanan terikat dengan kategori berikut:
    • Kemampuan pengelolaan pesanan dimigrasikan ke layanan pesanan.
    • Kemampuan pengelolaan pengiriman logistik bermigrasi ke layanan pengiriman.
    • Kemampuan inventaris dimigrasikan ke layanan inventaris.
  • Kemampuan akuntansi terikat dalam satu kategori:
    • Kemampuan konsumen, penjual, dan pihak ketiga terikat bersama dan bermigrasi ke layanan akun.

Memprioritaskan layanan untuk migrasi

Titik awal yang ideal untuk memisahkan layanan adalah dengan mengidentifikasi modul yang dikaitkan secara longgar dalam aplikasi monolitik Anda. Anda dapat memilih modul yang dikaitkan secara longgar sebagai salah satu kandidat pertama yang akan melakukan konversi ke microservice. Untuk menyelesaikan analisis dependensi setiap modul, lihat hal berikut:

  • Jenis dependensi: dependensi dari data atau modul lain.
  • Skala dependensi: bagaimana perubahan dalam modul yang teridentifikasi dapat memengaruhi modul lain.

Memigrasikan modul dengan dependensi data yang berat biasanya merupakan tugas yang sulit. Jika memigrasikan fitur terlebih dahulu lalu memigrasikan data terkait tersebut, Anda mungkin akan membaca dan menulis data ke beberapa database untuk sementara. Oleh karena itu, Anda harus memperhitungkan tantangan integritas dan sinkronisasi data.

Sebaiknya Anda mengekstrak modul yang memiliki persyaratan resource berbeda dibandingkan dengan monolit lainnya. Misalnya, jika modul memiliki database dalam memori, Anda dapat mengonversinya menjadi layanan, yang kemudian dapat di-deploy di host yang memiliki memori lebih tinggi. Jika Anda mengubah modul dengan persyaratan resource tertentu menjadi layanan, Anda dapat membuat aplikasi lebih mudah diskalakan.

Dari sudut pandang operasi, pemfaktoran ulang modul ke layanannya sendiri juga berarti menyesuaikan struktur tim yang ada. Jalur terbaik untuk menghapus akuntabilitas adalah dengan memberdayakan tim kecil yang memiliki seluruh layanan.

Faktor tambahan yang dapat memengaruhi cara Anda memprioritaskan layanan untuk migrasi mencakup kekritisan bisnis, cakupan pengujian yang komprehensif, postur keamanan aplikasi, dan dukungan organisasi. Berdasarkan evaluasi Anda, Anda dapat memberi peringkat layanan seperti yang dijelaskan dalam dokumen pertama dalam rangkaian ini, berdasarkan manfaat yang Anda terima dari pemfaktoran ulang.

Mengekstrak layanan dari monolit

Setelah mengidentifikasi kandidat layanan yang ideal, Anda harus mengidentifikasi cara agar modul microservice dan monolitik dapat saling berdampingan. Salah satu cara untuk mengelola koeksistensi ini adalah dengan memperkenalkan adaptor komunikasi antar-proses/inter-process communication (IPC), yang dapat membantu modul bekerja sama. Seiring dengan waktu, microservice mengambil beban dan menghilangkan komponen monolitik. Proses inkremental ini mengurangi risiko perpindahan dari aplikasi monolitik ke microservice baru karena Anda dapat mendeteksi bug atau masalah performa secara bertahap.

Diagram berikut menunjukkan cara mengimplementasikan pendekatan IPC:

Pendekatan IPC diimplementasikan untuk membantu modul bekerja sama.

Gambar 2. Adaptor IPC mengoordinasikan komunikasi antara aplikasi monolitik dan modul microservice.

Pada Gambar 2, modul Z adalah kandidat layanan yang ingin Anda ekstrak dari aplikasi monolitik. Modul X dan Y bergantung pada modul Z. Modul microservice X dan Y menggunakan adaptor IPC dalam aplikasi monolitik untuk berkomunikasi dengan modul Z melalui REST API.

Dokumen berikutnya dalam rangkaian ini, Komunikasi antarlayanan dalam penyiapan microservice, menjelaskan pola Stangler Fig dan cara mendekonstruksi layanan dari monolit.

Mengelola database monolitik.

Biasanya, aplikasi monolitik memiliki database monolitiknya sendiri. Salah satu prinsip arsitektur microservice adalah memiliki satu database untuk setiap microservice. Oleh karena itu, saat memodernisasi aplikasi monolitik menjadi microservice, Anda harus membagi database monolitik berdasarkan batas layanan yang Anda identifikasi.

Untuk menentukan lokasi pemisahan database monolitik, analisis pemetaan database terlebih dahulu. Sebagai bagian dari analisis ekstraksi layanan , Anda mengumpulkan beberapa insight tentang microservice yang perlu dibuat. Anda dapat menggunakan pendekatan yang sama untuk menganalisis penggunaan database dan memetakan tabel atau objek database lainnya ke microservice baru. Alat seperti SchemaCrawler, SchemaSpy, dan ERBuilder dapat membantu Anda melakukan suatu analisis. Tabel pemetaan dan objek lainnya akan membantu Anda memahami pengaitan antara objek database yang mencakup seluruh batas microservice potensial Anda.

Namun, membagi database monolitik adalah proses yang kompleks karena mungkin tidak ada pemisahan yang jelas antar-objek database. Anda juga perlu mempertimbangkan masalah lainnya, seperti sinkronisasi data, integritas transaksional, join, dan latensi. Bagian berikutnya menjelaskan pola yang dapat membantu Anda merespons masalah ini saat membagi database monolitik.

Tabel referensi

Dalam aplikasi monolitik, modul umumnya mengakses data yang diperlukan dari modul berbeda melalui join SQL ke tabel modul lain. Diagram berikut menggunakan contoh aplikasi e-commerce sebelumnya untuk menampilkan proses akses bergabung SQL ini:

Sebuah modul menggunakan gabungan SQL untuk mengakses data dari modul lain.

Gambar 3. Modul menggabungkan data ke tabel modul lain.

Pada gambar 3, untuk mendapatkan informasi produk, modul pesanan menggunakan kunci asing product_id untuk menggabungkan pesanan ke tabel produk.

Namun, jika Anda mendekonstruksi modul sebagai layanan individual, sebaiknya jangan meminta layanan pesanan untuk memanggil database layanan produk secara langsung untuk menjalankan operasi gabungan. Bagian berikut menjelaskan opsi yang dapat Anda pertimbangkan untuk memisahkan objek database.

Berbagi data melalui API

Saat memisahkan fungsi atau modul inti menjadi microservice, biasanya Anda menggunakan API untuk berbagi dan menampilkan data. Layanan yang direferensikan mengekspos data sebagai API yang diperlukan layanan panggilan, seperti yang ditunjukkan dalam diagram berikut:

Data diekspos melalui API.

Gambar 4. Layanan menggunakan panggilan API untuk mendapatkan data dari layanan lain.

Dalam gambar 4, modul pesanan menggunakan panggilan API untuk mendapatkan data dari modul produk. Implementasi ini memiliki masalah performa yang jelas karena panggilan jaringan dan database tambahan. Namun, berbagi data melalui API akan berfungsi dengan baik ketika ukuran data terbatas. Selain itu, jika layanan yang dipanggil menampilkan data yang memiliki tingkat perubahan yang terkenal, Anda dapat menerapkan cache TTL lokal pada pemanggil untuk mengurangi permintaan jaringan ke layanan yang dipanggil.

Mereplikasi data

Cara lain untuk berbagi data antara dua microservice terpisah adalah dengan mereplikasi data dalam database layanan dependen. Replikasi data bersifat hanya baca dan dapat dibuat ulang kapan saja. Pola ini memungkinkan layanan menjadi lebih kohesif. Diagram berikut menunjukkan cara kerja replikasi data di antara dua microservice:

Data direplikasi antar- microservice.

Gambar 5. Data dari layanan direplikasi dalam database layanan dependen.

Pada Gambar 5, database layanan produk direplikasi ke database layanan pesanan. Implementasi ini memungkinkan layanan pesanan mendapatkan data produk tanpa panggilan berulang ke layanan produk.

Untuk membangun replikasi data, Anda dapat menggunakan teknik seperti tampilan terwujud, mengubah pengambilan data (CDC), dan notifikasi peristiwa. Data yang direplikasi pada akhirnya konsisten, tetapi mungkin ada jeda dalam mereplikasi data, sehingga ada risiko menayangkan data yang sudah tidak berlaku.

Data statis sebagai konfigurasi

Data statis, seperti kode negara dan mata uang yang didukung, lambat untuk berubah. Anda dapat memasukkan data statis tersebut seperti konfigurasi dalam microservice. Microservice dan framework cloud modern menyediakan fitur untuk mengelola data konfigurasi tersebut menggunakan server konfigurasi, penyimpanan nilai kunci, dan vault. Anda dapat menyertakan fitur ini secara deklaratif.

Data yang dapat diubah bersama

Aplikasi monolitik memiliki pola umum yang dikenal sebagai status bersama yang dapat diubah. Dalam konfigurasi status bersama yang dapat diubah, beberapa modul menggunakan satu tabel, seperti yang ditunjukkan pada diagram berikut:

Konfigurasi status bersama yang dapat diubah akan membuat satu tabel tersedia untuk beberapa modul.

Gambar 6. Beberapa modul menggunakan satu tabel.

Pada Gambar 6, fungsi pesanan, pembayaran, dan pengiriman pada aplikasi e-commerce menggunakan tabel ShoppingStatus yang sama untuk mempertahankan status pesanan pelanggan selama perjalanan belanja.

Untuk memigrasikan monolit status bersama yang dapat diubah, Anda dapat mengembangkan microservice ShoppingStatus terpisah untuk mengelola tabel database ShoppingStatus. Layanan mikro ini mengekspos API untuk mengelola status belanja pelanggan, seperti yang ditunjukkan pada diagram berikut:

API diekspos ke layanan lain.

Gambar 7. Microservice mengekspos API ke beberapa layanan lainnya.

Dalam Gambar 7, microservice pembayaran, pesanan, dan pengiriman menggunakan API microservice ShoppingStatus. Jika tabel database berkaitan erat dengan salah satu layanan, sebaiknya pindahkan data ke layanan tersebut. Kemudian, Anda dapat mengekspos data melalui API untuk digunakan oleh layanan lain. Implementasi ini membantu Anda memastikan bahwa Anda tidak memiliki terlalu banyak layanan rinci yang sering memanggil satu sama lain. Jika Anda salah memisahkan layanan, Anda harus meninjau kembali penentuan batas layanannya.

Transaksi terdistribusi

Setelah Anda mengisolasi layanan dari monolit, transaksi lokal dalam sistem monolitik asli dapat didistribusikan di antara beberapa layanan. Transaksi yang mencakup beberapa layanan dianggap sebagai transaksi terdistribusi. Pada aplikasi monolitik, sistem database memastikan bahwa transaksi bersifat atomik. Untuk menangani transaksi antara berbagai layanan dalam sistem berbasis microservice, Anda perlu membuat koordinator transaksi global. Koordinator transaksi menangani rollback, tindakan kompensasi, dan transaksi lainnya yang dijelaskan dalam dokumen berikutnya dalam rangkaian ini, Komunikasi antar layanan dalam penyiapan microservice.

Konsistensi data

Transaksi terdistribusi memunculkan tantangan dalam mempertahankan konsistensi data di seluruh layanan. Semua update harus dilakukan secara atomik. Dalam aplikasi monolitik, properti transaksi menjamin bahwa kueri menampilkan tampilan database yang konsisten berdasarkan tingkat isolasinya.

Sebaliknya, pertimbangkan transaksi multi-langkah dalam arsitektur berbasis microservice. Jika ada satu transaksi layanan yang gagal, data harus direkonsiliasi dengan melakukan rollback langkah yang telah berhasil di seluruh layanan lain. Jika tidak, tampilan global data aplikasi di antara layanan tidak akan konsisten.

Mungkin akan sulit menentukan kapan langkah dengan penerapan konsistensi tertunda mengalami kegagalan. Misalnya, suatu langkah mungkin tidak langsung gagal, tetapi dapat terblokir atau kehabisan waktu. Oleh karena itu, Anda mungkin perlu menerapkan beberapa mekanisme waktu habis. Jika data duplikat sudah tidak berlaku saat layanan yang dipanggil mengaksesnya, data yang di-cache atau direplikasi antarlayanan untuk mengurangi latensi jaringan juga dapat menyebabkan data tidak konsisten.

Dokumen berikutnya dalam rangkaian ini, Komunikasi antarlayanan dalam penyiapan microservice, memberikan contoh pola untuk menangani transaksi terdistribusi di seluruh microservice.

Mendesain komunikasi antarlayanan

Dalam aplikasi monolitik, komponen (atau modul aplikasi) memanggil satu sama lain secara langsung melalui panggilan fungsi. Sebaliknya, aplikasi berbasis microservice terdiri dari beberapa layanan yang berinteraksi satu sama lain melalui jaringan.

Saat Anda mendesain komunikasi interservice, pertama-tama pikirkan tentang perkiraan layanan akan berinteraksi satu sama lain. Interaksi layanan dapat berupa salah satu dari hal berikut:

  • Interaksi one-to-one: setiap permintaan klien diproses oleh tepat satu layanan.
  • Interaksi one-to-many: setiap permintaan diproses oleh beberapa layanan.

Pertimbangkan juga apakah interaksinya sinkron atau asinkron/tidak sinkron:

  • Sinkron: klien mengharapkan respons tepat waktu dari layanan dan mungkin akan diblokir saat menunggu.
  • Asinkron: klien tidak melakukan pemblokiran saat menunggu respons. Jika sudah ada respons yang diterima, tidak harus segera dikirim.

Tabel berikut menampilkan kombinasi gaya interaksi:

One-to-one One-to-many
Sinkron Permintaan dan respons: mengirim permintaan ke layanan dan menunggu respons.
Asinkron Notifikasi: mengirim permintaan ke layanan, tetapi tidak ada balasan yang diharapkan atau dikirim. Publikasikan dan berlangganan: klien memublikasikan pesan notifikasi, dan tidak ada atau beberapa layanan yang tertarik akan memakai pesan tersebut.
Permintaan dan respons asinkron: mengirim permintaan ke layanan, yang membalas secara asinkron. Klien tidak melakukan pemblokiran. Memublikasikan dan respons asinkron: klien memublikasikan permintaan, dan menunggu respons dari layanan yang tertarik.

Setiap layanan biasanya menggunakan kombinasi dari gaya interaksi tersebut.

Mengimplementasikan komunikasi antarlayanan

Untuk mengimplementasikan komunikasi antarlayanan, Anda dapat memilih berbagai teknologi IPC. Misalnya, layanan menggunakan mekanisme komunikasi berbasis permintaan respons sinkron seperti REST, gRPC, atau Thrift berbasis HTTP. Atau, layanan menggunakan mekanisme komunikasi berbasis pesan yang asinkron seperti AMQP atau STOMP. Anda juga dapat memilih berbagai format pesan. Misalnya, layanan menggunakan format berbasis teks yang dapat dibaca manusia, seperti JSON atau XML. Atau, layanan menggunakan format biner seperti Avro atau Protocol Buffer.

Mengonfigurasi layanan untuk langsung memanggil layanan lain akan menghasilkan pengaitan yang tinggi di antara layanan. Sebagai gantinya, sebaiknya gunakan fitur pesan atau komunikasi berbasis peristiwa:

  • Fitur Pesan: Saat menerapkan pesan, Anda menghapus kebutuhan akan layanan untuk memanggil satu sama lain secara langsung. Sebaliknya, semua layanan mengetahui tentang broker pesan, dan mereka mengirim pesan ke broker tersebut. Broker pesan menyimpan pesan terkait dalam antrean. Layanan lain dapat berlangganan ke pesan yang mereka maksud.
  • Komunikasi berbasis peristiwa: Ketika Anda menerapkan pemrosesan berbasis peristiwa, komunikasi antar-layanan terjadi melalui peristiwa yang dihasilkan oleh setiap layanan. Masing-masing layanan menulis peristiwa ke perantara pesan. Layanan dapat memproses peristiwa yang diinginkan. Pola ini membuat layanan tetap dikaitkan secara longgar karena peristiwanya tidak menyertakan payload.

Pada aplikasi microservice, sebaiknya gunakan komunikasi antarlayanan asinkron, bukan komunikasi sinkron. Permintaan-respons adalah pola arsitektur yang dapat dipahami dengan baik, sehingga mendesain API sinkron mungkin terasa lebih alami daripada mendesain sistem asinkron. Komunikasi asinkron antara layanan dapat diimplementasikan menggunakan fitur pesan atau komunikasi berbasis peristiwa. Penggunaan komunikasi asinkron memberikan keuntungan berikut:

  • Pengaitan longgar: Model asinkron membagi interaksi permintaan-respons menjadi dua pesan terpisah, satu untuk permintaan dan satu lagi untuk respons. Konsumen layanan memulai pesan permintaan dan menunggu respons, lalu penyedia layanan menunggu pesan permintaan yang dibalas dengan pesan respons. Dengan pengaturan ini berarti pemanggil tidak perlu menunggu pesan respons.
  • Isolasi kegagalan: Pengirim masih dapat terus mengirim pesan meskipun konsumen downstream gagal. Konsumen mengambil backlog setiap kali melakukan pemulihan. Kemampuan ini sangat berguna dalam arsitektur microservice, karena setiap layanan memiliki siklus prosesnya masing-masing. Namun, API sinkron mengharuskan ketersediaan layanan downstream atau operasi gagal.
  • Responsivitas: Layanan upstream dapat membalas lebih cepat jika tidak menunggu di layanan downstream. Jika ada rantai dependensi layanan (layanan A memanggil B, yang memanggil C, dll.), menunggu panggilan sinkron dapat menambahkan jumlah latensi yang tidak dapat diterima.
  • Kontrol alur: Antrean pesan bertindak sebagai buffering, sehingga penerima dapat memproses pesan sesuai kecepatannya sendiri.

Namun, berikut adalah beberapa tantangan dalam menggunakan pesan asinkron secara efektif:

  • Latensi: Jika broker pesan menjadi bottleneck, latensi menyeluruh mungkin menjadi tinggi.
  • Overhead dalam pengembangan dan pengujian: Berdasarkan pilihan infrastruktur pesan atau peristiwa, ada kemungkinan adanya pesan duplikat, yang menyulitkan untuk menjadikan operasi bersifat idempoten. Selain itu, mungkin sulit untuk mengimplementasikan dan menguji semantik respons permintaan menggunakan pesan asinkron. Anda memerlukan cara untuk menghubungkan pesan permintaan dan respons.
  • Throughput: Penanganan pesan asinkron, baik menggunakan antrean pusat maupun mekanisme lainnya dapat menjadi hambatan dalam sistem. Sistem backend, seperti antrean dan konsumen downstream, harus diskalakan agar sesuai dengan persyaratan throughput sistem.
  • Merumitkan penanganan error: Dalam sistem asinkron, pemanggil tidak tahu apakah permintaan berhasil atau gagal, sehingga penanganan error perlu ditangani di luar band. Jenis sistem ini dapat mempersulit penerapan logika seperti percobaan ulang atau back-off eksponensial. Penanganan error lebih rumit jika ada beberapa panggilan asinkron berantai yang harus berhasil atau gagal.

Dokumen berikutnya dalam rangkaian ini, Komunikasi antarlayanan dalam penyiapan microservice, menyediakan implementasi referensi untuk mengatasi beberapa tantangan yang disebutkan dalam daftar sebelumnya.

Langkah selanjutnya