[Backend Masterclass] PowerSync Setup Guide: Tuyệt kỹ xây dựng hệ thống Local-First "Mất mạng vẫn chạy ầm ầm"
Anh em làm backend cho các hệ thống bán lẻ, e-commerce hay app giao hàng chắc chắn từng đau đầu với bài toán: "App mất mạng thì nhân viên quét mã vạch kiểu gì? Khách hàng xem giỏ hàng ra sao?" Giải pháp cũ là nhét data vào Redux, Async Storage hay LocalStorage... cực kì chắp vá và dễ dính lỗi "conflict data" khi có mạng trở lại. Đó là lúc PowerSync xuất hiện như một đấng cứu thế. Nó mang sức mạnh đồng bộ (Sync) tự động giữa Database Backend (PostgreSQL) và Local Database dưới App (SQLite). Mất mạng? App vẫn chạy ầm ầm trên SQLite. Có mạng? Tự động đồng bộ lên server siêu tốc qua cơ chế CDC.
Dưới đây là hướng dẫn setup PowerSync từ A-Z, chuẩn kĩ sư hệ thống, dọn đường cho anh em đập đi xây lại cái tư duy làm app offline truyền thống.
1. Configure Your Source Database (Cấu hình Database gốc)
PowerSync sinh ra là để làm bạn thân với PostgreSQL. Nó không query "trâu bò" vào DB của bạn mỗi giây để xem có gì thay đổi đâu (thế thì sập mẹ server). Nó đứng hóng biến dựa trên WAL (Write-Ahead Log) thông qua cơ chế Logical Replication (Giống hệt cách Debezium/Kafka hoạt động).
Anh em mở file postgresql.conf lên và sửa ngay tham số sinh tử này:
wal_level = logical
max_replication_slots = 5
max_wal_senders = 5
Khởi động lại Postgres. Giờ đây, mỗi lệnh INSERT/UPDATE/DELETE của anh em đều được ghi vào một cái log để PowerSync có thể "bắt mạch" realtime theo từng mili-giây.
2. Set Up PowerSync Service Instance
Anh em có 2 lựa chọn: Xài hàng Cloud (PowerSync host sẵn) hoặc Tự host (Self-hosted) bằng Docker. Để nhanh gọn nhẹ trong bài này, anh em lên PowerSync Dashboard, tạo một Project mới. Nó sẽ cấp cho anh em một con Service Instance. Đây chính là "trạm trung chuyển" đứng giữa Postgres của anh em và hàng vạn cái App mobile ở dưới.
3. Connect PowerSync to Your Source Database
Đừng dại dột đưa tài khoản postgres (Superadmin) cho PowerSync. Tư duy kĩ sư hệ thống là nguyên tắc đặc quyền tối thiểu (Principle of Least Privilege).
Hãy tạo một user riêng trong Postgres dành riêng cho PowerSync:
CREATE ROLE powersync_role WITH LOGIN PASSWORD 'super_secret_password' REPLICATION;
-- Cấp quyền truy cập vào schema public
GRANT USAGE ON SCHEMA public TO powersync_role;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO powersync_role;
-- Cho phép nó đọc replication
GRANT rds_replication TO powersync_role; -- (Nếu dùng AWS RDS)
Quay lại PowerSync Dashboard, ném chuỗi URI kết nối vào: postgres://powersync_role:password@your-db-host:5432/your_db. PowerSync sẽ chọc vào, check kết nối và tạo ra một Publication (Bản xuất bản) ở DB của bạn.
4. Define Sync Streams (Trái tim của hệ thống)
Đây là bước quan trọng nhất! App dưới client xài SQLite, bộ nhớ điện thoại thì có hạn. Anh em không thể bốc toàn bộ cục DB 100GB ném xuống điện thoại người dùng được!
Sync Streams (được viết bằng file sync_rules.yaml) sinh ra để quy định: Thằng User A thì được phép tải những data nào về máy của nó?
Ví dụ một file config phân quyền phân mảnh dữ liệu (Data Partitioning):
# sync_rules.yaml
bucket_definitions:
# Cụm data riêng của từng User (Giỏ hàng, Đơn hàng cá nhân)
user_data:
parameters:
- request.user_id
data:
- select * from orders where user_id = request.user_id
- select * from cart_items where user_id = request.user_id
# Cụm data chung (Danh sách sản phẩm public)
global_data:
data:
- select id, name, price from products where status = 'active'
PowerSync sẽ dựa vào rules này để chia nhỏ Postgres ra thành hàng triệu cái "bucket" siêu nhỏ, ông nào login vào thì chỉ đẩy đúng bucket của ông đó xuống. Quá đỉnh!
5. Generate a Development Token
Để App dưới client nối được với PowerSync Service, nó cần một cái JWT (JSON Web Token). Bình thường Backend của anh em sẽ có một API GET /api/powersync-token để sinh ra token này. Nhưng lúc setup dev, anh em lười code Backend thì cứ lên thẳng PowerSync Dashboard, bấm "Generate Dev Token", nhập một cái user_id giả lập vào để test trước đã.
6. [Optional] Test Sync với Sync Diagnostics Client
Đừng vội đụng vào code Mobile/Frontend. Hãy kiểm tra xem cái ống nước (Pipeline) từ Postgres -> PowerSync đã thông chưa.
PowerSync cung cấp một công cụ là Diagnostics App. Anh em paste cái URL Instance và cái Dev Token vừa tạo vào đó.
Mở DBeaver/PgAdmin, thử INSERT một dòng vào bảng products. Nếu trên màn hình Diagnostics nảy số, thấy data bay về ngay lập tức thì chúc mừng, backend đã setup hoàn hảo!
Đừng vội đụng vào code Mobile/Frontend. Hãy kiểm tra xem cái ống nước (Pipeline) từ Postgres -> PowerSync đã thông chưa.
PowerSync cung cấp một công cụ là Diagnostics App. Anh em paste cái URL Instance và cái Dev Token vừa tạo vào đó.
Mở DBeaver/PgAdmin, thử INSERT một dòng vào bảng products. Nếu trên màn hình Diagnostics nảy số, thấy data bay về ngay lập tức thì chúc mừng, backend đã setup hoàn hảo!
7. Use the Client SDK (Phía đầu cầu Client)
Đây là lúc anh em gọi đội Mobile (React Native / Flutter) hoặc Frontend (React/Vue) vào việc. Bản chất là chúng ta sẽ thao tác với SQLite ở dưới máy khách.
Install the Client SDK
Ví dụ với JS/TypeScript (React Native/Web):
npm install @journeyapps/powersync-sdk-web @electric-sql/pglite
Define Your Client-Side Schema
Client cần biết cấu trúc bảng để tạo SQLite nội bộ. Schema này phải map với cái Sync Streams anh em đã định nghĩa ở Bước 4.
import { Schema, Table, column } from '@journeyapps/powersync-sdk-web';
export const AppSchema = new Schema([
new Table('products', [
column.text('name'),
column.integer('price')
]),
new Table('orders', [
column.text('user_id'),
column.integer('total_amount')
])
]);
Instantiate the PowerSync Database
Khởi tạo instance cho database nội bộ:
import { PowerSyncDatabase } from '@journeyapps/powersync-sdk-web';
const powerSync = new PowerSyncDatabase({
schema: AppSchema,
database: { dbFilename: 'my_offline_app.sqlite' }
});
Connect to PowerSync Service Instance
Client không thể cứ thế mà nối thẳng vào PowerSync. Anh em phải implement một cái Connector để xử lý việc fetch Token từ Backend và quy định luồng đẩy dữ liệu.
powerSync.connect(new MyBackendConnector());
// MyBackendConnector sẽ tự động fetch Dev Token (bước 5)
// và giữ kết nối WebSockets với PowerSync Service để hứng data Real-time.
Read Data
Lúc này, thay vì gọi API fetch('/products'), app sẽ query thẳng vào cái SQLite nằm ngay trên RAM/Ổ cứng của điện thoại. Tốc độ trả về là 0ms!
Hơn nữa, SDK hỗ trợ Live Queries (Reactive). Data trên server đổi -> SQLite đổi -> UI tự động re-render không cần refresh.
// Query trực tiếp từ SQLite local
const products = await powerSync.getAll('SELECT * FROM products');
// Hoặc Watch realtime
powerSync.watch('SELECT * FROM products', [], (results) => {
console.log("Data vừa thay đổi theo thời gian thực:", results);
});
Write Data
Đây là CÚ LỪA lớn nhất của PowerSync mà anh em phải hiểu rõ bản chất: PowerSync CHỈ đồng bộ MỘT CHIỀU từ Postgres xuống SQLite (Read-only từ góc nhìn của PowerSync Service). Vậy khi User bấm "Thêm vào giỏ hàng" (Write) lúc mất mạng thì sao?
- Lệnh Write đó sẽ đập thẳng vào bảng tạm trong SQLite local (Khách hàng vẫn thấy app phản hồi ngay lập tức).
- Lệnh đó đồng thời được tống vào một Offline Queue (Hàng đợi cục bộ).
- Khi có mạng trở lại, cái
MyBackendConnectorcủa anh em sẽ móc từng lệnh trong Queue đó ra, GỌI REST API/GRAPHQL lên con Backend của anh em (chứ không chọc vào PowerSync). - Backend của anh em (Laravel/Go/Nodejs) nhận API, xử lý business logic, ghi vào Postgres.
- Postgres ghi xong -> WAL sinh ra -> PowerSync Service hốt được -> Bắn ngược lại xuống mọi device khác để cập nhật trạng thái.
Một vòng lặp khép kín, hoàn hảo và an toàn tuyệt đối về mặt dữ liệu!
Viết đến đây chắc anh em cũng hình dung được sức mạnh hủy diệt của kiến trúc Local-First rồi. Không còn độ trễ mạng, không còn màn hình loading xoay xoay vô tận.
Mọi thứ anh em cần để vứt bỏ lối mòn tư duy API-First truyền thống đều nằm trọn trong 7 bước trên. Anh em nào chuẩn bị áp dụng vào dự án thực tế mà gặp ca khó về phân quyền (Sync Rules) hay xử lý Conflict khi Write, cứ ném câu hỏi vào đây anh em ta mổ xẻ tiếp nhé!
All rights reserved