Modul penutup jalur ini mengerjakan satu proyek utuh dari keputusan pertama sampai sistem yang berjalan, dan keputusan pertama itu adalah pemilihan proyek. Tiga kandidat telah disiapkan, yaitu chat dengan presence, kanban, dan dashboard langsung papan status sensor suhu. Topik ini membandingkan ketiganya dari sisi jenis beban, konsep yang digunakan, dan tingkat kesulitannya, kemudian mengulas tiga cara mengelola state karena pilihan state menentukan arsitektur yang menyusul.
Tiga kandidat proyek#
Ketiga kandidat menggunakan rangkaian perkakas yang sama, yaitu Mix, Phoenix, LiveView, dan Ecto, tetapi beban utamanya berbeda sehingga menuntut kemampuan yang berbeda pula. Chat dengan presence mengalirkan pesan dua arah secara real-time melalui WebSocket, dan setiap kamar menampilkan daftar pengguna yang sedang daring. Kanban memindahkan kartu antar kolom, dan setiap perpindahan harus terlihat oleh pengguna lain yang membuka papan yang sama pada saat yang hampir bersamaan. Dashboard langsung papan status sensor suhu menerima aliran data satu arah dari sensor, menampilkan nilai terkini beserta riwayatnya, dan tidak menerima perubahan apa pun dari halamannya.
Tabel berikut merangkum ketiganya. Kolom jenis beban menyatakan sumber tekanan utama proyek, kolom konsep menyebut konsep-konsep jalur ini yang menjadi tulang punggung kandidat, sedangkan kolom tingkat kesulitan menilai keseluruhan kemampuan yang harus siap sejak hari pertama.
| Kandidat | Jenis beban | Konsep yang digunakan | Tingkat kesulitan |
|---|---|---|---|
| Chat dengan presence | pesan dua arah secara real-time plus daftar pengguna daring per kamar | GenServer per kamar, PubSub, presence | tinggi |
| Kanban | perpindahan kartu antar kolom yang terlihat semua pengguna | Ecto, PubSub, LiveView | sedang |
| Dashboard papan status sensor suhu | aliran data satu arah yang masuk berkala | PubSub, Stream, LiveView | lebih ringan |
Panduan memilih berdasarkan konsep#
Kriteria pemilihan yang paling berguna adalah konsep yang ingin diperdalam, bukan panjang daftar fitur. Setiap kandidat melatih kumpulan konsep yang berbeda, dan urutan tingkat kesulitan pada tabel di atas sekaligus menjadi urutan latihan yang disarankan. Panduan berikut menghubungkan setiap kandidat dengan konsep yang paling banyak dilatihnya:
- konsep GenServer, Registry, dan process buatan runtime: pilih chat, karena setiap kamar adalah process yang dibuat dan dimatikan saat runtime.
- konsep Ecto dan changeset pada data terstruktur: pilih kanban, karena kartu dan kolomnya menyimpan data yang harus tahan restart.
- konsep Stream dan agregasi: pilih dashboard, karena riwayat suhu diolah dari aliran nilai yang panjang.
- keseimbangan data, real-time, dan antarmuka: pilih kanban, karena ketiganya disentuh tanpa menuntut fitur presence.
Apabila seluruh konsep di atas terasa sama beratnya, mulailah dari kandidat yang lebih ringan lalu naikkan tingkatnya setelah titik pemeriksaan terakhir tercapai, karena urutan dari dashboard menuju chat menekan jumlah konsep baru yang muncul bersamaan. Kandidat yang dipilih menentukan isi titik pemeriksaan pada pengerjaannya. Keputusan ini lebih baik diambil sekali dengan alasan yang jelas daripada diganti di tengah pengerjaan.
Tiga cara mengelola state#
Pemilihan proyek pada akhirnya bermuara pada satu pertanyaan teknis, yaitu di mana state disimpan dan siapa yang boleh menyentuhnya. Jalur ini telah menyiapkan tiga jawaban yang tersusun mulai dari yang paling sederhana. Map yang dipegang satu variabel lokal hanya hidup di dalam satu process, Agent menyimpan state di process terpisah tanpa kontrak pesan, sedangkan GenServer bernama menambahkan antarmuka fungsi yang dapat dipanggil dari modul mana pun. Skrip berikut memperlihatkan cara ketiganya mengelola papan tugas yang isinya sama:
# tiga_state.exs — tiga cara menyimpan state papan tugas sederhana
defmodule Papan do
use GenServer
def start_link(_arg), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
def tambah(id, status), do: GenServer.cast(__MODULE__, {:tambah, id, status})
def isi, do: GenServer.call(__MODULE__, :isi)
@impl true
def init(state), do: {:ok, state}
@impl true
def handle_cast({:tambah, id, status}, state), do: {:noreply, Map.put(state, id, status)}
@impl true
def handle_call(:isi, _dari, state), do: {:reply, state, state}
end
# cara 1: map yang dipegang satu variabel lokal pada satu process tunggal
papan_lokal =
%{}
|> Map.put(:a1, "ditinjau")
|> Map.put(:a2, "selesai")
IO.inspect(papan_lokal, label: "map proses tunggal")
# cara 2: Agent, process penyimpan state tanpa kontrak pesan
{:ok, agent} = Agent.start_link(fn -> %{} end)
Agent.update(agent, &Map.put(&1, :a1, "ditinjau"))
Agent.update(agent, &Map.put(&1, :a2, "selesai"))
IO.inspect(Agent.get(agent, & &1), label: "agent tanpa nama")
# cara 3: GenServer bernama, antarmukanya dapat dipanggil dari mana saja
{:ok, _} = Papan.start_link([])
Papan.tambah(:a1, "ditinjau")
Papan.tambah(:a2, "selesai")
IO.inspect(Papan.isi(), label: "GenServer bernama")map proses tunggal: %{a1: "ditinjau", a2: "selesai"}
agent tanpa nama: %{a1: "ditinjau", a2: "selesai"}
GenServer bernama: %{a1: "ditinjau", a2: "selesai"}Skrip yang sama dapat dijalankan dari terminal setelah berkasnya disimpan:
# menjalankan skrip perbandingan state
elixir tiga_state.exsmap proses tunggal: %{a1: "ditinjau", a2: "selesai"}
agent tanpa nama: %{a1: "ditinjau", a2: "selesai"}
GenServer bernama: %{a1: "ditinjau", a2: "selesai"}Ketiga keluarannya identik, sedangkan perbedaannya terletak pada kontrak yang tidak tampak. Papan pada variabel lokal hilang bersama processnya. Agent dapat dijangkau hanya melalui pid yang dipegang pemanggilnya, sehingga tidak ada cara resmi menghubunginya dari modul lain. GenServer bernama dijangkau melalui namanya, dan pola inilah yang digunakan process kamar pada kandidat chat melalui Registry. Dari sini satu prinsip pemilihan state sudah terbentuk, yaitu memilih berdasarkan siapa yang harus dapat mengaksesnya, bukan berdasarkan jumlah kode yang harus ditulis.
Kesalahan umum#
Kesalahan pertama adalah memilih proyek dari daftar fiturnya semata. Proyek yang dituntun oleh fitur menghasilkan pengerjaan tanpa arah belajar yang jelas, dan konsep baru sering muncul bersamaan pada saat yang tidak terduga. Perbaikannya adalah memutuskan konsep yang ingin diperdalam terlebih dahulu, lalu mencocokkannya dengan tabel kandidat di atas.
Kesalahan kedua adalah menilai kesulitan proyek dari jumlah fiturnya. Presence adalah satu fitur yang tampak kecil, tetapi fitur itu menuntut WebSocket, Registry, dan PubSub bekerja bersama sejak awal. Bandingkan kandidat dari jenis bebannya, bukan dari hitungan fiturnya, karena satu beban berat hampir selalu lebih sulit daripada banyak beban ringan.
Kesalahan ketiga adalah menumpuk seluruh state di dalam process. Process kehilangan seluruh state yang disimpannya ketika mati dan dihidupkan ulang oleh supervisor, sehingga kartu kanban yang hilang setelah restart merupakan bug, bukan keanehan. Data yang harus bertahan disimpan melalui Ecto di database, sedangkan process hanya menyimpan state sesaat seperti daftar pengguna yang sedang daring.
Ringkasan#
- Tiga kandidat proyek dibedakan oleh jenis bebannya, yaitu pesan dua arah dengan presence, perubahan papan kolaboratif, dan aliran data satu arah.
- Kriteria pemilihan terbaik adalah konsep yang ingin diperdalam, dengan urutan kesulitan dari dashboard, kanban, hingga chat.
- Map lokal, Agent, dan GenServer bernama menghasilkan isi state yang sama dengan kontrak akses yang berbeda, dan pilihannya mengikuti siapa yang harus dapat mengakses state tersebut.
- Data yang harus tahan restart disimpan melalui Ecto, sedangkan process hanya menyimpan state sesaat.
Referensi#
- Modul Phoenix.Presence — Hexdocs Phoenix — mekanisme presence dan dependensinya pada PubSub dan Registry.
- Modul Phoenix.PubSub — Hexdocs Phoenix — saluran broadcast yang menjadi dasar ketiga kandidat.
- Modul Agent — Hexdocs Elixir — abstraksi state sederhana yang digunakan skrip perbandingan.
- Modul GenServer — Hexdocs Elixir — kontrak callback GenServer bernama yang menjadi pintu domain.