CQRS
Ketika data di sistem kita sudah mulai besar, dan performa databasenya sudah mulai tidak optimal lagi. Entah itu proses write nya yang mulai lambat, atau mungkin proses read nya yang mulai lambat. Bisa jadi ini saatnya teman-teman untuk mengimplementasikan sebuah solusi bernama CQRS.
CQRS / Command Query Responsibility Segregation adalah pola arsitektur yang memisahkan perintah command (write) dan juga query (read), hal ini dilakukan agar performa database kita menjadi lebih optimal. Karena nantinya kita akan memisahkan proses read dan write secara independent, sehingga sistem kita akan jadi lebih scalable karena data akan disimpan di storage yang berbeda.
Bagaimana cara mengimplementasikan CQRS? yaitu dengan memisahkan service untuk melakukan proses query (read) dan command (write), dimana dalam servicenya nanti akan terbagi dua yaitu untuk proses write yang akan dikoneksikan dengan database master nya dan proses read yang akan dikoneksikan dengan database replica nya atau dengan database berbeda yang mempunyai performa yang baik untuk proses read data (misal elasticsearch, dimana proses sync data nya nanti akan dilakukan menggunakan message broker atau cronjob yang sumber datanya akan diambil dari database master nya).
Untuk lebih menggambarkan masalah yang akan terjadi jika kita tidak menerapkan CQRS dalam sistem kita, adalah sebagai berikut :
Dari masalah diatas kita bisa melihat bahwa sistem kita akan kepayahan ketika melakukan proses read dan juga write ke database, oleh karena itu kita akan coba implementasi CQRS untuk lebih mengoptimalkan kinerja sistem kita. Dan CQRS juga sebenarnya memiliki banyak strategi untuk implementasinya, dan berikut merupakan beberapa cara yang akan saya coba jelaskan :
- CQRS — Master (Write) & Replica (Read) Database
2. CQRS — Master (Write) Database & Database Lain (Read) yang Memiliki Performa yang Baik untuk Read Data (Misal Elasticsearch)
Dari beberapa strategi CQRS yang saya gambarkan diatas, saya akan coba contohkan bagaimana implementasi CQRS dengan memisahkan proses write (master database) dan proses read (replica database) di bahasa pemrograman ruby menggunakan framework sinatra :
Get Solehudin MQ’s stories in your inbox
Join Medium for free to get updates from this writer.
Backend Code
- Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
# Gemfile
source "https://rubygems.org"
gem "sinatra"
gem "activerecord", "~> 7.0"
gem "pg" # PostgreSQL driver
gem "sinatra-activerecord"
group :development, :test do
gem "byebug"
end
gem "rackup", "~> 2.2"
gem "puma", "~> 7.1"- config/database.yml
development:
primary:
adapter: postgresql
encoding: unicode
database: coba_replica
username: postgres
password: postgres
host: 127.0.0.1
port: 5432
replica:
adapter: postgresql
encoding: unicode
database: coba_replica
username: postgres
password: postgres
host: 127.0.0.1
port: 5433- main.rb
require "sinatra"
require "sinatra/activerecord"
require "yaml"
require "active_record"
# Load configs
db_config = YAML.load_file("config/database.yml")["development"]
# Connect to primary (master) for write
ActiveRecord::Base.establish_connection(db_config["primary"])
class ReplicaBase < ActiveRecord::Base
self.abstract_class = true
end
# Connect to primary (replica) for read
ReplicaBase.establish_connection db_config["replica"]
# Define model untuk write ke database master
class CommandContact < ActiveRecord::Base
self.table_name = "contacts"
end
# Define model untuk read ke database replica
class QueryContact < ReplicaBase
self.table_name = "contacts"
end
# Command - Tulis ke database master
post "/contacts" do
data = JSON.parse(request.body.read)
contact = CommandContact.create(data["contact"])
status 201
content_type :json
{ data: contact, success: 1 }.to_json
end
# Query - Baca dari replica
get "/contacts" do
contacts = QueryContact.all()
content_type :json
{ data: contacts }.to_json
end- Setup :
bundle install- Run Sinatra App :
bundle exec ruby main.rb- Contoh Request Create Product :
curl --location 'http://127.0.0.1:4567/contacts' \
--header 'Content-Type: application/json' \
--data-raw '{
"contact": {
"first_name": "ramli2",
"last_name": "ronde2",
"email": "ramli2@local.com",
"phone": "+6232323232"
}
}'- Contoh Request Get Products :
curl --location 'http://127.0.0.1:4567/contacts'Jika teman-teman ingin mencoba code diatas dan bingung cara setup database replication di PostgreSQL di localhost, berikut saya ada step by step nya (bukan untuk production) :
sudo mkdir /var/lib/postgresql/16/primary
sudo mkdir /var/lib/postgresql/16/replica
sudo chown -R postgres:postgres /var/lib/postgresql/16/primary
sudo chown -R postgres:postgres /var/lib/postgresql/16/replica
sudo -u postgres /usr/lib/postgresql/16/bin/initdb -D /var/lib/postgresql/16/primary
ls -ld /var/lib/postgresql/16/replica
sudo nano /var/lib/postgresql/16/primary/postgresql.conf
# start content
listen_addresses = '*'
port = 5432
wal_level = replica
max_wal_senders = 4
max_replication_slots = 1
hot_standby = on
# end content
sudo -u postgres nano /var/lib/postgresql/16/primary/pg_hba.conf
# start content
host replication replication_user 127.0.0.1/32 scram-sha-256
# end content
sudo systemctl stop postgresql
sudo -u postgres /usr/lib/postgresql/16/bin/pg_ctl start -D /var/lib/postgresql/16/primary -l /var/lib/postgresql/16/primary/primary.log
sudo ss -nlt | grep 5432
sudo -u postgres /usr/lib/postgresql/16/bin/pg_ctl stop -D /var/lib/postgresql/16/replica
sudo -u postgres rm -rf /var/lib/postgresql/16/replica/*
# login to psql cli
sudo -u postgres psql
CREATE USER replication_user WITH REPLICATION LOGIN ENCRYPTED PASSWORD 'replication_user';
SELECT * FROM pg_create_physical_replication_slot('replica_slot');
SELECT slot_name, slot_type, active FROM pg_replication_slots;
# logout from psql cli
sudo -u postgres pg_basebackup -h 127.0.0.1 -p 5432 -U replication_user -D /var/lib/postgresql/16/replica -F p -X stream -R -W -v --slot=replica_slot
sudo -u postgres nano /var/lib/postgresql/16/replica/postgresql.conf
# start content
port = 5433
# end content
sudo chmod 0700 /var/lib/postgresql/16/replica
sudo -u postgres /usr/lib/postgresql/16/bin/pg_ctl start -D /var/lib/postgresql/16/replica -l /var/lib/postgresql/16/replica/replica.log
sudo ss -nlt | grep 5433
# login to psql cli
sudo -u postgres psql
SELECT client_addr, state, sync_state, application_name
FROM pg_stat_replication;
CREATE DATABASE coba_replica;
\c coba_replica;
CREATE TABLE contacts (
contact_id uuid DEFAULT gen_random_uuid(),
first_name VARCHAR NOT NULL,
last_name VARCHAR NOT NULL,
email VARCHAR NOT NULL,
phone VARCHAR,
PRIMARY KEY (contact_id)
);
# logout from psql cliJika teman-teman ingin melihat contoh code nya secara full, di bawah ini merupakan implementasi CQRS yang saya coba buat dengan bahasa ruby : segregato.
Nah teman-teman tadi itu merupakan penjelasan mengenai CQRS, mudah-mudahan bisa bermanfaat. Jika ada hal yang salah dari apa yang saya sampaikan, silahkan sampaikan di kolom komentar agar kita sama-sama belajar. Segitu saja dulu teman-teman tulisan saya kali ini, enjoy!









