T5 — Thiết kế cơ sở dữ liệu (ER Diagram) - congsinhv/fluxion GitHub Wiki
Thiết Kế Cơ Sở Dữ Liệu (ER Diagram)
Issue: #26 — Thiết kế cơ sở dữ liệu (ER Diagram) Tuần: 5 | 21/04 – 27/04/2026
Numbering chính thức: Mục 3.6 theo Master TOC
Revision 2026-04-19: Follow-up audit.
- Bỏ column
error_counttrongbatch_actions(errorCount COMPUTED ở GraphQL resolver, không store)- Clarify batch semantics: track DISPATCH outcome (action-trigger publish SNS), không track device execution callback
- Bỏ timeout logic (không cần — dispatch synchronous)
Revision 2026-04-18: Bổ sung 4 bảng mới sau khi review wireframes T8b.
brands,tacs: Type Allocation Code management (wireframeconfiguration-tac)batch_actions,batch_device_actions: Replace conceptaction_logscũ — tracking batch-level summary + per-device status để generate CSV error report on-demand từ S3message_templates: schema overhaul — thaylocalebằngnotification_type(FULLSCREEN/POPUP) + 6 cột lưuicons(3 slots × 2 fields) +is_activesoft delete- Tracking issue: #55
3.6 Thiết Kế Cơ Sở Dữ Liệu
3.6.1 ER Diagram
erDiagram
services ||--o{ policies : "service_type_id"
services ||--o{ actions : "service_type_id"
states ||--o{ policies : "state_id"
states ||--o{ actions : "from_state_id"
policies ||--o{ actions : "apply_policy_id"
policies ||--o{ devices : "current_policy_id"
policies ||--o{ milestones : "policy_id"
devices ||--|| device_informations : "device_id"
devices ||--|| device_tokens : "device_id"
devices ||--o{ action_executions : "device_id"
devices ||--o{ milestones : "device_id"
actions ||--o{ devices : "assigned_action_id"
actions ||--o{ milestones : "assigned_action_id"
actions ||--o{ action_executions : "action_id"
users ||--o{ chat_sessions : "user_id"
chat_sessions ||--o{ chat_messages : "session_id"
brands ||--o{ tacs : "brand_id"
actions ||--o{ batch_actions : "action_id"
batch_actions ||--o{ batch_device_actions : "batch_id"
devices ||--o{ batch_device_actions : "device_id"
services {
smallint id PK
varchar name UK
bool is_enabled
timestamptz created_at
}
states {
smallint id PK
varchar name UK
timestamptz created_at
}
policies {
smallint id PK
varchar name
smallint service_type_id FK
smallint state_id FK
varchar color
timestamptz created_at
}
actions {
uuid id PK
varchar name
smallint action_type_id
smallint from_state_id FK
smallint service_type_id FK
smallint apply_policy_id FK
jsonb configuration
jsonb ext_fields
timestamptz created_at
}
devices {
uuid id PK
smallint state_id FK
smallint current_policy_id FK
uuid assigned_action_id FK
timestamptz created_at
timestamptz updated_at
}
device_informations {
uuid id PK
uuid device_id FK
varchar serial_number UK
varchar udid UK
varchar name
varchar model
varchar os_version
float battery_level
varchar wifi_mac
bool is_supervised
timestamptz last_checkin_at
timestamptz created_at
timestamptz updated_at
jsonb ext_fields
}
device_tokens {
uuid id PK
uuid device_id FK
bytea push_token
varchar push_magic
bytea unlock_token
varchar topic
timestamptz created_at
timestamptz updated_at
}
action_executions {
uuid id PK
uuid device_id FK
uuid action_id FK
uuid command_uuid UK
varchar status
timestamptz created_at
timestamptz updated_at
jsonb ext_fields
}
milestones {
uuid id PK
uuid device_id FK
uuid assigned_action_id FK
smallint policy_id FK
timestamptz created_at
jsonb ext_fields
}
users {
uuid id PK
varchar email UK
varchar name
varchar role
varchar cognito_sub UK
bool is_active
timestamptz created_at
timestamptz updated_at
}
chat_sessions {
uuid id PK
uuid user_id FK
timestamptz created_at
timestamptz updated_at
}
chat_messages {
uuid id PK
uuid session_id FK
varchar role
text content
jsonb tool_calls
jsonb tool_result
timestamptz created_at
}
message_templates {
uuid id PK
varchar name
text content
varchar notification_type
bool is_active
varchar notification_icon_path
varchar notification_icon_name
varchar header_icon_path
varchar header_icon_name
varchar additional_icon_path
varchar additional_icon_name
timestamptz created_at
timestamptz updated_at
}
brands {
serial id PK
varchar name UK
timestamptz created_at
timestamptz updated_at
}
tacs {
uuid id PK
varchar tac_code UK
varchar provisioning_type
int brand_id FK
varchar model
varchar marketing_name
timestamptz created_at
timestamptz updated_at
}
batch_actions {
uuid id PK
uuid batch_id UK
uuid action_id FK
varchar created_by
int total_devices
varchar status
timestamptz created_at
timestamptz updated_at
}
batch_device_actions {
uuid id PK
uuid batch_id FK
uuid device_id FK
varchar status
varchar error_code
text error_message
timestamptz started_at
timestamptz finished_at
timestamptz created_at
}
3.6.2 Table Definitions
3.6.2.1 FSM Tables (services/states/policies/actions)
Đã định nghĩa chi tiết trong T5 — DeviceFSM mục 3.4 (DB-driven FSM).
| Table | Rows | Mô tả |
|---|---|---|
services |
~3 | Service types: Inventory, Supply Chain, Postpaid |
states |
6 | FSM states: Idle → Registered → Enrolled → Active ⇄ Locked → Released |
policies |
~6 | Mỗi policy gắn với 1 state + 1 service |
actions |
~12 | Transitions implicit: from_state_id → apply_policy_id |
milestones |
Growing | Device history — mỗi state change tạo 1 record |
3.6.2.2 devices (Fluxion business data)
CREATE TABLE devices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
state_id SMALLINT NOT NULL REFERENCES states(id) DEFAULT 1,
current_policy_id SMALLINT REFERENCES policies(id),
assigned_action_id UUID REFERENCES actions(id), -- NULL = not busy
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Chỉ chứa Fluxion business fields: FSM state, policy, assigned action. Không chứa Apple MDM data.
3.6.2.3 device_informations (Apple MDM query data)
CREATE TABLE device_informations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL UNIQUE REFERENCES devices(id) ON DELETE CASCADE,
serial_number VARCHAR(50) UNIQUE NOT NULL,
udid VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(200),
model VARCHAR(100), -- iPhone 15 Pro, iPad Air M2
os_version VARCHAR(20), -- 17.4.1, 18.0
battery_level REAL, -- 0.0 → 1.0
wifi_mac VARCHAR(20),
is_supervised BOOLEAN DEFAULT FALSE,
last_checkin_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ext_fields JSONB -- additional MDM query fields
);
Data source: Response từ DeviceInformation MDM command (server chủ động gửi command hỏi device).
3.6.2.4 device_tokens (Apple MDM protocol tokens)
CREATE TABLE device_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL UNIQUE REFERENCES devices(id) ON DELETE CASCADE,
push_token BYTEA NOT NULL, -- APNS Token (binary)
push_magic VARCHAR(200) NOT NULL, -- PushMagic string for APNS payload
unlock_token BYTEA, -- UnlockToken for ClearPasscode (optional)
topic VARCHAR(200) NOT NULL, -- Topic device subscribes to
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Data source: Device TokenUpdate check-in message. Updated khi device enrollment hoặc iOS rotate push token.
Event: DEVICE_TOKEN_UPDATE → checkin-handler UPSERT.
3.6.2.5 action_executions
CREATE TABLE action_executions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES devices(id),
action_id UUID NOT NULL REFERENCES actions(id),
command_uuid UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(), -- match MDM CommandUUID
status VARCHAR(20) NOT NULL DEFAULT 'ACTION_PENDING',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ext_fields JSONB -- { "failed_reason": "...", "error_chain": [...] }
);
-- status values: ACTION_PENDING, ACTION_COMPLETED, ACTION_FAILED
command_uuid: unique ID gửi tới device trong MDM command. Device trả result kèm CommandUUID → OEM match và publish event → BE checkin-handler tìm action_executions by command_uuid.
3.6.2.6 users
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'operator', -- admin, operator
cognito_sub VARCHAR(100) UNIQUE NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
3.6.2.7 chat_sessions & chat_messages
CREATE TABLE chat_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE chat_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES chat_sessions(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL, -- user, assistant, tool
content TEXT,
tool_calls JSONB, -- [{id, name, arguments}]
tool_result JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
3.6.2.8 message_templates
CREATE TABLE message_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
content TEXT NOT NULL, -- plain text, no rendering
notification_type VARCHAR(20) NOT NULL, -- FULLSCREEN | POPUP
is_active BOOLEAN NOT NULL DEFAULT TRUE, -- soft delete
notification_icon_path VARCHAR(512) NOT NULL, -- S3 key / CDN URL
notification_icon_name VARCHAR(255) NOT NULL,
header_icon_path VARCHAR(512) NOT NULL,
header_icon_name VARCHAR(255) NOT NULL,
additional_icon_path VARCHAR(512) NOT NULL,
additional_icon_name VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Resolver flow khi action có messageTemplateId: resolver load content → gán vào payload.messageContent (plain string, as-is) → enqueue. Worker không rendering.
3.6.2.9 brands + tacs (Type Allocation Code)
CREATE TABLE brands (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE, -- "iPhone", "Galaxy", ...
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE tacs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tac_code VARCHAR(20) NOT NULL UNIQUE, -- 8-digit code
provisioning_type VARCHAR(50) NOT NULL, -- "Apple", "Android"
brand_id INT REFERENCES brands(id) ON DELETE SET NULL,
model VARCHAR(100), -- "A2650"
marketing_name VARCHAR(255), -- "iPhone 14 Pro"
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Seed
INSERT INTO brands (name) VALUES ('iPhone');
INSERT INTO tacs (tac_code, provisioning_type, brand_id, model, marketing_name)
VALUES ('35387910', 'Apple', 1, 'A2650', 'iPhone 14 Pro');
3.6.2.10 batch_actions + batch_device_actions
Batch-level tracking cho bulk assign action. 1 row trong batch_actions / batch; N rows trong batch_device_actions / batch (chỉ device có error khi dispatch). FE query batch_actions → summary; mutation generateActionLogErrorReport(batchId) tạo CSV từ batch_device_actions WHERE error_code IS NOT NULL → upload S3 → pre-signed URL (TTL 5 phút). errorCount ở GraphQL được COMPUTED dynamic từ COUNT, không store column trong batch_actions.
CREATE TABLE batch_actions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
batch_id UUID NOT NULL UNIQUE,
action_id UUID NOT NULL REFERENCES actions(id),
created_by VARCHAR(255) NOT NULL, -- userId
total_devices INT NOT NULL,
status VARCHAR(20) NOT NULL, -- IN_PROGRESS | COMPLETED | FAILED
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE batch_device_actions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
batch_id UUID NOT NULL REFERENCES batch_actions(batch_id) ON DELETE CASCADE,
device_id UUID NOT NULL REFERENCES devices(id),
status VARCHAR(20) NOT NULL, -- PENDING | SUCCESS | FAILED (dispatch outcome)
error_code VARCHAR(100), -- NULL nếu dispatch thành công
error_message TEXT,
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Status semantics (dispatch-only, không track device callback):
action-resolverINSERTbatch_actions.status = IN_PROGRESS+ N rowsbatch_device_actions.status = PENDINGaction-triggerworker pick up từng device SQS message:- Valid (publish SNS →
apple-process-action-sqsthành công) →batch_device_actions.status = SUCCESS - Invalid (device offline, không có push_token, invalid transition…) →
batch_device_actions.status = FAILED+error_code+error_message
- Valid (publish SNS →
- Khi tất cả rows done:
- 0 errors →
batch_actions.status = COMPLETED - All errors →
batch_actions.status = FAILED - Mixed →
batch_actions.status = COMPLETED(không có PARTIAL_ERROR)
- 0 errors →
- Không cần timeout — dispatch là synchronous, không phụ thuộc device callback. Device execution status track riêng qua
action_executions+milestones.
3.6.3 Indexes
-- devices
CREATE INDEX idx_devices_state_id ON devices(state_id);
CREATE INDEX idx_devices_current_policy_id ON devices(current_policy_id);
CREATE INDEX idx_devices_assigned ON devices(assigned_action_id) WHERE assigned_action_id IS NOT NULL;
-- device_informations
CREATE UNIQUE INDEX idx_di_device_id ON device_informations(device_id);
-- device_tokens
CREATE UNIQUE INDEX idx_dt_device_id ON device_tokens(device_id);
-- action_executions
CREATE INDEX idx_ae_device_id ON action_executions(device_id);
CREATE UNIQUE INDEX idx_ae_command_uuid ON action_executions(command_uuid);
CREATE INDEX idx_ae_active ON action_executions(status) WHERE status NOT IN ('ACTION_COMPLETED', 'ACTION_FAILED');
-- milestones
CREATE INDEX idx_milestones_device_id ON milestones(device_id);
CREATE INDEX idx_milestones_created_at ON milestones(created_at);
-- chat
CREATE INDEX idx_chat_sessions_user_id ON chat_sessions(user_id);
CREATE INDEX idx_chat_messages_session ON chat_messages(session_id, created_at);
-- message_templates
CREATE INDEX idx_message_templates_notification_type ON message_templates(notification_type);
CREATE INDEX idx_message_templates_is_active ON message_templates(is_active);
-- tacs
CREATE INDEX idx_tacs_tac_code ON tacs(tac_code);
CREATE INDEX idx_tacs_brand_id ON tacs(brand_id);
-- batch_actions
CREATE INDEX idx_batch_actions_status ON batch_actions(status);
CREATE INDEX idx_batch_actions_created_at ON batch_actions(created_at DESC);
CREATE INDEX idx_batch_device_actions_batch_id ON batch_device_actions(batch_id);
CREATE INDEX idx_batch_device_actions_status ON batch_device_actions(status);
3.6.4 Table Summary
| Table | Type | Separation | Growth |
|---|---|---|---|
services |
Config | — | Static |
states |
Config | — | Static |
policies |
Config | — | Static |
actions |
Config | — | Rare |
devices |
Core | Fluxion business | Slow |
device_informations |
Core | Apple MDM query data | Slow |
device_tokens |
Core | Apple MDM protocol tokens | Slow |
action_executions |
Transactional | — | Medium |
milestones |
History | — | Medium |
users |
Core | — | Rare |
chat_sessions |
Transactional | — | Slow |
chat_messages |
Transactional | — | Medium |
message_templates |
Config | — | Rare |
brands |
Config | — | Static |
tacs |
Config | — | Rare |
batch_actions |
Transactional | — | Medium |
batch_device_actions |
Transactional | — | Fast |
3.6.4.1 Device Data Separation
devices → Fluxion business: state, policy, assigned_action
device_informations → Apple MDM: serial, udid, model, os_version, battery
device_tokens → Apple MDM protocol: push_token, push_magic, unlock_token
Lý do tách:
- Separation of concerns: Fluxion data vs Apple MDM data
- Update frequency: tokens update thường xuyên, device info update khi query, business data update khi action
- Security:
device_tokenschứa sensitive data (unlock_token) - OEM independence: Nếu đổi 3rd party (iOS → Android), chỉ thay
device_informations+device_tokens, không đụngdevices
Kết Luận
Schema cơ sở dữ liệu Fluxion MDM gồm 17 bảng phân chia thành 4 nhóm chức năng với ranh giới rõ ràng. Nhóm FSM Config (services, states, policies, actions) mã hóa toàn bộ logic chuyển trạng thái thiết bị — đây là trái tim của DB-driven FSM, cho phép thay đổi transitions qua SQL mà không cần deploy lại code. Nhóm Device Data (devices, device_informations, device_tokens, action_executions, milestones) áp dụng nguyên tắc tách biệt Fluxion business data khỏi Apple MDM protocol data, tạo điều kiện cho OEM independence. Nhóm User & Chat (users, chat_sessions, chat_messages) hỗ trợ RBAC và conversation memory cho NLP chatbot.
Các quyết định chuẩn hóa (normalization) đáng chú ý: device_informations và device_tokens là bảng riêng biệt (1-1 với devices) thay vì columns trong devices — phân tách tần suất cập nhật và nhóm quyền truy cập. milestones là append-only history table, không bao giờ UPDATE — đảm bảo audit trail bất biến. action_executions.command_uuid có UNIQUE constraint để match với Apple MDM CommandUUID, cho phép idempotent processing của duplicate SQS messages.
Bộ indexes được thiết kế theo access patterns thực tế: partial index trên action_executions(status) chỉ index các records đang active (loại bỏ completed/failed), composite index trên chat_messages(session_id, created_at) tối ưu sliding window query. Schema đạt chuẩn 3NF cho tất cả bảng, sử dụng JSONB (ext_fields, configuration) cho dữ liệu semi-structured linh hoạt mà không phá vỡ normalization của core fields.
Tài Liệu Tham Khảo
[1] Elmasri, R. & Navathe, S. Fundamentals of Database Systems, 7th ed. Pearson, 2015.
[2] Date, C. J. An Introduction to Database Systems, 8th ed. Addison-Wesley, 2003.
[3] PostgreSQL Documentation. https://www.postgresql.org/docs/16/