Hands-on Implementation: xây dựng một bản triển khai có thể vận hành

    Cài đặt, kết nối Postgres, viết data model đầu tiên, gọi API, sau đó xây dựng một dashboard Angular và một ứng dụng low-code.

    Trần Ngọc Đăng KhôiChương 03 / 0720 phút đọc
    Mục lục chương
      Ý chính của chươngMột dự án Cube gồm ba thành phần: biến môi trường để kết nối database, các file data model trong thư mục model và client gọi REST API. Khi cả ba thành phần đã cấu hình đúng, giao diện sẽ hiển thị số liệu.
      03.1

      Chuẩn bị môi trường

      Cài Cube CLI, tạo dự án, chạy Postgres và Cube bằng Docker Compose.

      Handbook dùng Cube CLI để sinh khung dự án với driver Postgres, sau đó dùng Docker Compose khởi chạy Postgres và Cube ở chế độ nền. Đánh dấu từng bước sau khi hoàn thành; trình duyệt này lưu lại trạng thái đánh dấu.

      Ba bước cài đặt
      terminal
      npm install -g cubejs-cli
      terminal
      cubejs create ng-demo -d postgres
      cd ng-demo
      terminal
      docker-compose up -d
      Hình 1Checklist cài đặt. Nút Copy sao chép lệnh tương ứng.
      Ghi nhớCờ -d postgres trong lệnh tạo dự án chọn database driver. Cờ -d trong docker-compose up mang nghĩa khác: chạy ở chế độ nền (detached).
      03.2

      Kết nối nguồn dữ liệu

      Sáu biến môi trường cho kết nối, một biến cho bảo mật API.

      Cube đọc thông tin kết nối từ file .env. Nguồn dữ liệu trong ví dụ là Postgres, chạy local hoặc trên cloud. Chọn từng dòng để xem chức năng của biến tương ứng. Biến cuối cùng, CUBEJS_API_SECRET, không phục vụ kết nối mà dùng để kiểm tra token của client. Các giá trị trong ví dụ là placeholder, cần thay bằng giá trị thực tế.

      .env
      Hình 2Mỗi dòng .env là một biến; chọn một dòng để xem giải thích.

      Sau khi kết nối, cần nạp dữ liệu mẫu. Handbook gợi ý hai bộ: bản dump thương mại điện tử ecom-dump.sql, hoặc bộ US Superstore 9.994 dòng. Data model ở mục sau dùng bảng orders theo cấu trúc Superstore.

      Đọc sâuLệnh nạp dữ liệu và cấu trúc bảng orders

      Nạp bản dump mẫu vào một database mới:

      terminal
      curl http://cube.dev/downloads/ecom-dump.sql > ecom-dump.sql
      createdb ecom
      psql --dbname ecom -f ecom-dump.sql

      Bảng orders theo cấu trúc Superstore. Mỗi dòng là một sản phẩm trong đơn nên order_id có thể lặp lại; do đó primary key là row_num.

      SQL
      CREATE TABLE orders (
          row_num INT PRIMARY KEY,
          order_id TEXT,
          order_date TIMESTAMP WITHOUT TIME ZONE,
          ship_date TIMESTAMP WITHOUT TIME ZONE,
          ship_mode TEXT,
          customer_id TEXT,
          customer_name TEXT,
          segment TEXT,
          country TEXT,
          city TEXT,
          state TEXT,
          postal_code TEXT,
          region TEXT,
          product_id TEXT,
          category TEXT,
          sub_category TEXT,
          product_name TEXT,
          sales NUMERIC,
          quantity NUMERIC,
          discount NUMERIC,
          profit NUMERIC
      );
      03.3

      Data model đầu tiên

      Một file YAML cho bảng orders và một file JavaScript cho bảng users.

      Từ v0.33, Cube chuẩn hóa ba quy ước: thư mục model tách thành model/cubes (các cube gốc) và model/views (data mart); tên theo snake_case cho cả YAML và JavaScript; dùng sql_table thay cho câu SELECT * dư thừa. Chọn từng dòng trong hai file dưới đây để xem giải thích.

      Hình 3Hai cube viết bằng hai cú pháp: orders bằng YAML (measures, dimensions, segments), users bằng JavaScript (measures, dimensions).
      Ghi nhớunique_orders dùng count_distinct trên order_id vì một đơn có nhiều dòng. Measure kiểu count trả về số dòng sản phẩm, không phải số đơn.
      03.4

      Truy vấn qua Cube

      Gửi JSON query tới REST API và nhận kết quả JSON với tên trường tương ứng.

      REST API nhận truy vấn ở endpoint /cubejs-api/v1/load, kèm JWT trong header Authorization. Truy vấn liệt kê measures, dimensions và timeDimensions; mỗi trường trong kết quả giữ nguyên tên đã khai báo trong truy vấn. Chọn một dòng ở bên trái để làm nổi bật dòng tương ứng bên phải.

      query
      response
      Hình 4Truy vấn và kết quả, nối với nhau theo tên trường.
      Ghi nhớKhi truy vấn có granularity, kết quả có thêm khóa mang hậu tố granularity, ví dụ orders.order_date.month.
      03.5

      Tích hợp vào ứng dụng frontend

      Ví dụ dùng Angular: cấu hình client một lần, mỗi biểu đồ theo dõi một query.

      Handbook đặt tên mục này theo Next.js nhưng ví dụ thực tế dùng Angular, với các thư viện @cubejs-client/core, @cubejs-client/ngx và ng2-charts. Module gốc cấu hình client một lần. Mỗi component biểu đồ đẩy query vào một Subject; client theo dõi Subject đó, gọi API và trả kết quả về. Sau đó chartPivot chuyển kết quả sang định dạng thư viện biểu đồ dùng được.

      terminal
      npm install -s ng2-charts @cubejs-client/core @cubejs-client/ngx moment

      ng2-charts vẽ biểu đồ, @cubejs-client/core gọi API, @cubejs-client/ngx bọc client thành service của Angular.

      Hình 5Ba bước tích hợp. Ở hai tab code, chọn từng dòng để xem giải thích.
      03.6

      Dashboard phân tích cơ bản

      Năm ô, một component dùng lại, khác nhau ở chartType, query và title.

      Dashboard gồm ba ô số đơn lẻ và hai biểu đồ theo thời gian. Tất cả các ô dùng chung component app-chart. Chọn từng ô để xem thẻ khai báo tương ứng.

      Hình 6Bố cục dashboard trong app.component.html. Các ô để trống vì đây là khung bố cục, không chứa dữ liệu thực.
      Đọc sâuTemplate của chart component

      Template xử lý hai trạng thái: khi chưa có dữ liệu thì hiển thị spinner; khi đã có dữ liệu thì vẽ canvas biểu đồ hoặc hiển thị một con số đơn lẻ.

      chart.component.html
      <div class="card">
        <div class="card-body">
          <h5 class="card-title">{{ title }}</h5>
          <div class="card-text">
            <div *ngIf="ready === false" class="d-flex justify-content-center text-dark">
              <div class="spinner-border" role="status">
                <span class="sr-only">Loading...</span>
              </div>
            </div>
            <canvas *ngIf="ready && showChart" baseChart height="300"
                    [datasets]="chartData" [labels]="chartLabels"
                    [options]="chartOptions" [chartType]="chartType"></canvas>
            <h1 *ngIf="ready && !showChart" height="300">{{ chartData }}</h1>
          </div>
        </div>
      </div>

      Template dùng showChart, chartLabels và chartOptions, nhưng đoạn chart.component.ts trong handbook không gán ba biến này. Khi triển khai thực tế cần bổ sung phần gán này, ví dụ đặt showChart theo chartType.

      03.7

      Kiểm tra kết quả truy vấn

      Developer Playground hiển thị query, câu SQL được sinh ra, độ trễ và trạng thái cache.

      Trước khi sử dụng số liệu trên dashboard, cần kiểm tra lại trong Developer Playground tại http://localhost:4000. Bốn bước dưới đây trình bày quy trình từ chọn trường đến đọc độ trễ.

      Hình 7Quy trình kiểm tra trong Playground. SQL ở bước 3 là minh họa. Tên các mục theo phiên bản Playground trong handbook; Playground hiện tại đặt câu SQL ở tab Generated SQL và hiển thị thời gian truy vấn ở góc trên bên phải.
      Ghi nhớKhi số liệu bất thường, cần đọc câu SQL trước tiên. Phần lớn lỗi nằm ở join hoặc bộ lọc, và đều thấy rõ trong câu SQL.
      03.8

      Bài lab: Bubble.io gọi Cube REST API

      Một ứng dụng low-code lấy số liệu theo phân khúc khách hàng qua REST.

      Bubble.io dùng plug-in API Connector để gọi Cube. Truy vấn có một tham số động là segment, đổi theo dropdown trên màn hình. Thực hiện lần lượt các bước và đánh dấu sau khi hoàn thành.

      Các bước trong Bubble.io
      Hình 8Checklist bài lab; trình duyệt này lưu lại trạng thái đánh dấu.
      Giá trị dropdown đang chọn
      payload gửi đi

      Hình 9Payload thay đổi theo dropdown.
      Chương tiếp theo04 · Engineering Deep Dives→

      Token và mật khẩu trong ví dụ đã thay bằng placeholder.